Kandria Quest System

This document outlines the ideas and concepts behind Kandria's quest system to help you understand how the story must be structured and defined in order to integrate with the game.

The gist of the system is as follows: the story in the game is divided up into individual quests. Each quest has a number of tasks that should be completed in order to complete the quest, and each task has a number of triggers it can spawn into the world.

Quest

A quest is a simple construct that has a description of itself for the player's logbook, a set of total tasks associated with it, and a number of tasks that are activated when the quest itself is activated.

Quests should be used for bigger overarching story beats, analogous to chapters in a traditional story. A quest can contain branching narrative within its tasks, and multiple quests may be active at the same time.

Task

A task is much more involved and the primary workhorse that outlines the narrative structure of the game. Each task has a short description (:description) and title (:title) for the player to read, a set of triggers that are activated when this task is activated (:on-activate), a condition that causes the task to be completed if fulfilled (:condition), an invariant that causes the task to be failed if unfulfilled (:invariant), a set of triggers that are activated if the task is completed (:on-complete), and a marker for where the player should go on the map to do the task (:marker).

While the game is running a task can be in one of the following states:

Tasks are strung together to form a non-linear narrative by having a task cause multiple other tasks.

Multiple tasks can also cause the same task, in which case they become alternative ways of advancing the plot. As soon as the player completes one of the possible tasks, the others become obsoleted.

The task condition allows more complex behaviours to trigger completion of a task outside of dialogue. The task invariant allows you to ensure tasks only remain active while they can still be completed in a sensible fashion. Both the condition and invariant can be arbitrary Lisp code.

The task marker should be a list composed of a name for another entity that defines the marker's center, and a size of the marker in number of tiles. Eg :marker (hub 100) would place a marker centered on the hub chunk, 100 tiles in size, to make it more ambiguous.

Triggers

Triggers are an abstract concept for "things that happen when tasks change state". Triggers come in a couple of different types, outlined here.

Quests and Tasks

Quests and tasks can act as triggers themselves, and will simply become active when triggered.

Interactions

An interaction is a piece of dialogue that the player can initiate with a particular NPC or item. The set of active quests determines the set of available interactions with the NPCs and items, allowing you to control relevant dialogue and story. Each interaction should have an :interactable that it is attached to, and a :title that is used to display to the user in a menu when multiple interactions are possible.

See the dialogue format documentation for more information on how dialogue is written.

Actions

An action executes a piece of arbitrary Lisp code when activated or deactivated. This can be useful to do things such as spawning items and enemies, giving the player rewards, etc.

Variable Bindings

The entire storyline, as well as each quest, task, and interaction can specify a set of variable bindings (:variables). This should be a list of bindings like so: (a (b T) (c :something)) which would bind three variables, a, b, and c, each being set to NIL, T, and :something respectively.

Bindings follow the same scoping as outlined above. This means that when accessing a variable (through var), first the bindings of the closest relevant structure are used (typically an interaction). If no binding matches the requested name, the parent is considered instead, all the way up to the storyline.

If a variable is retrieved that does not exist, NIL is returned on read, and a new binding is created on the closest relevant structure on write. Writing variables that were not previously declared is bad style however, and a warning will be logged as a consequence.

Updating State Live

You can redefine quests, tasks, and triggers while the game is running. Changes to existing structures should reflect immediately.

If you want to change the status of a structure, you should use quest:update. You can simply type out a form in any Lisp file and use the recompile command to get it going. After recompiling it should also print the updated state of the quest system, showing only active items.

Sequence Quests

Often it is useful to define a simple sequence of tasks within a linear quest. This can be done much more conveniently using define-sequence-quest. Here's two simple examples:

;; 1. Go to cave
;; 2. Find mushroom
;; 3. Hand in mushroom
(define-sequence-quest (kandria mushrooms)
  :title "Get some mushrooms"
  (:go-to (cave)
   :title "Go to cave"
   "
   ~ catherine
   | Good luck out there!")
  (:have (item:mushroom 10)
   :title "Find mushroom")
  (:interact (catherine)
   :title "Return the mushroom"
   "
   ~ catherine
   | Thanks!"))

;; 1. Go to first leak
;; 2. Listen to interaction
;; 3. Defeat enemies
;; 4. Go to second leak
;; 5. Defeat enemies
;; 6. Listen to interaction
(define-sequence-quest (kandria leak)
  :title "a"
  (:go-to (leak1 :lead catherine)
   :title "go to leak"
   "| A")
  (:interact (catherine :now T)
   "| Stuff")
  (:complete (spawner)
   :title "Defeat the enemies")
  (:go-to (leak2 :lead catherine)
   :title "go to leak 2"
   "| Stufff")
  (:complete (spawner2)
   :title "Defeat the enemies")
  (:interact (catherine :now T)
   "| Stuff"))

A sequence is defined as a list of clauses, with each clause defining a task with one or more triggers. The following clauses are recognised:

Default interactions

You can define default interactions to play back for NPC interactions when there's no other quest state active that would take precedence. This allows filling in "dead air" and avoids NPCs just completely ghosting the player outside of quests.

To define default interactions, use define-default-interaction like so:

(define-default-interactions jack
  (q2-seeds
   "| Be careful out there")
  (q0-settlement-arrive
   "| Just to be clear, I still don't trust you.")
  (T
   "| Who are you?"))

Meaning each body form is composed of a quest name and a piece of dialogue to execute if that quest has been completed. Note since the first matching piece will fire first, you need to order the snippets in reverse, such that the last quest (from the perspective of the overall storyline) is listed first.